﻿#region License

// Another free, open solution by folks who get it.
// Copyright © 2011 Business in Unison, Inc.
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

using AutoMapper;

using FizzWare.NBuilder;

using FluentAssertions;

using Patterns.Collections;
using Patterns.Mapping;
using Patterns.Text;

using TechTalk.SpecFlow;

namespace Patterns.Specifications.Collections
{
	[Binding]
	public class CollectionSteps
	{
		public IDictionary<string, object> PropertyBag
		{
			get { return ScenarioContext.Current.GetValue("propertyBag") as IDictionary<string, object>; }
			set { ScenarioContext.Current["propertyBag"] = value; }
		}

		static CollectionSteps()
		{
			Mapper.CreateMap<IDictionary, ComplexTestObject>().ConvertFromDictionary();
		}

		public static ICollection<ComplexTestObject> TestObjectCollection
		{
			get
			{
				ICollection<ComplexTestObject> collection = ScenarioContext.Current.Values.OfType<ICollection<ComplexTestObject>>().FirstOrDefault();
				collection.Should().NotBeNull();
				return collection;
			}
			set { ScenarioContext.Current[string.Format("testCollection{0}", Guid.NewGuid().ToString("N"))] = value; }
		}

		public static IDictionary<string, ComplexTestObject> TestObjectDictionary
		{
			get
			{
				Dictionary<string, ComplexTestObject> testDictionary = ScenarioContext.Current.Values.OfType<Dictionary<string, ComplexTestObject>>().FirstOrDefault();
				testDictionary.Should().NotBeNull();
				return testDictionary;
			}
			set { ScenarioContext.Current[string.Format("testDictionary{0}", Guid.NewGuid().ToString("N"))] = value; }
		}

		public static IDictionary<int, ThreadLog> ThreadLogs
		{
			get
			{
				Dictionary<int, ThreadLog> logs = ScenarioContext.Current.Values.OfType<Dictionary<int, ThreadLog>>().FirstOrDefault();
				logs.Should().NotBeNull();
				return logs;
			}
			set { ScenarioContext.Current[string.Format("threadLog{0}", Guid.NewGuid().ToString("N"))] = value; }
		}

		[Given(@"I have created a new collection containing (\d+) new complex test objects")]
		public void CreateTestObjectCollection(int itemCount)
		{
			TestObjectCollection = Builder<ComplexTestObject>.CreateListOfSize(itemCount).Build();
		}

		[When(@"I add (\d+) new complex test objects using the AddRange method")]
		public void TestObjectAddRange(int itemCount)
		{
			TestObjectCollection.AddRange(Builder<ComplexTestObject>.CreateListOfSize(itemCount).Build());
		}

		[Given(@"I have created a thread log dictionary for capturing thread Id and object hash code values")]
		public void CreateThreadLogDictionary()
		{
			ThreadLogs = new Dictionary<int, ThreadLog>();
		}

		[Given(@"I have created a new dictionary containing 1 new complex test object at key ""(\w+)""")]
		public void CreateTestObjectDictionaryWithOneItem(string key)
		{
			GeneralSteps.CurrentTestObject = Builder<ComplexTestObject>.CreateNew().Build();
			TestObjectDictionary = new Dictionary<string, ComplexTestObject> { { key, GeneralSteps.CurrentTestObject } };
		}

		[Given(@"I have created a new dictionary containing 0 new complex test objects")]
		public void CreateEmptyTestObjectDictionary()
		{
			TestObjectDictionary = new Dictionary<string, ComplexTestObject>();
		}

		[When(@"I add an entry to the thread log dictionary for each object in (parallel|serial)")]
		public void LogObjectThreads(string parallelOrSerial)
		{
			var @lock = new ReaderWriterLockSlim();
			TestObjectCollection.ForEach(parallel: parallelOrSerial.IsEquivalentTo("parallel"), action: testObject =>
			{
				int threadId = Thread.CurrentThread.ManagedThreadId;
				@lock.EnterUpgradeableReadLock();
				ThreadLog threadLog = ThreadLogs.GetValue(threadId) ?? new ThreadLog { ThreadId = threadId, HashCodes = new List<int>() };
				@lock.EnterWriteLock();
				threadLog.HashCodes.Add(testObject.GetHashCode());
				ThreadLogs[threadId] = threadLog;
				@lock.ExitWriteLock();
				@lock.ExitUpgradeableReadLock();
			});
		}

		[When(@"I attempt to get a value from the dictionary at key ""(\w+)""")]
		public void GetTestObjectDictionaryValue(string key)
		{
			GeneralSteps.CurrentResult = TestObjectDictionary.GetValue(key);
		}

		[When(@"I convert the object to a property bag")]
		public void ConvertObjectToPropertyBag()
		{
			PropertyBag = GeneralSteps.CurrentTestObject.AsPropertyBag();
		}

		[Then(@"the collection count should equal (\d+)")]
		public void CheckCollectionCount(int expectedCount)
		{
			TestObjectCollection.Count.Should().Be(expectedCount);
		}

		[Then(@"the thread log dictionary should have (\d+) thread(?:s)?")]
		public void CheckThreadLogDictionaryCount(int expectedCount)
		{
			ThreadLogs.Count.Should().Be(expectedCount);
		}

		[Then(@"thread \#(\d+) should have (\d+) hash code(?:s)?")]
		public void CheckThreadHashCount(int threadNumber, int expectedHashCount)
		{
			ThreadLog threadLog = ThreadLogs.Values.ElementAt(threadNumber - 1);
			threadLog.Should().NotBeNull();
			threadLog.HashCodes.Count.Should().Be(expectedHashCount);
		}

		[Then(@"all of the test object's properties should be the same as the property bag's values")]
		public void CheckPropertyBagValues()
		{
			GeneralSteps.CurrentTestObject.GetType().FindProperties().ForEach(property =>
			{
				object bagValue = PropertyBag.GetValue(property.Name);
				property.GetValue(GeneralSteps.CurrentTestObject, null).ShouldHave().AllProperties().EqualTo(bagValue);
			});
		}

		#region Nested type: ThreadLog

		public class ThreadLog
		{
			public int ThreadId { get; set; }
			public ICollection<int> HashCodes { get; set; }
		}

		#endregion
	}
}